' Strict
SuperStrict

' Modules
Import BRL.Max2D
'Import BtbN.GLDraw2D
Import BRL.PNGLoader
Import BRL.Reflection
Import BRL.Threads
Import PUB.FreeJoy

' Files
Import "../TGame.bmx"
Import "../TFrameRate.bmx"
Import "../TPlayer.bmx"
Import "../TEnemy.bmx"
Import "../TTileMap.bmx"
Import "../TINILoader.bmx"
Import "../TSoundManager.bmx"
Import "../TSlot.bmx"
Import "../TDamageView.bmx"
Import "../Utilities/Math.bmx"
Import "../Utilities/Graphics.bmx"
Import "../Utilities/Reflection.bmx"

' Global
Global gsInGame:TGameStateInGame

' Factor for Y coordinates
Const perspectiveFactor:Float = 0.75

' TGameStateInGame
Type TGameStateInGame Extends TGameState
	Field player:TPlayer
	Field map:TTileMap
	Field frameCounter:TFrameRate
	Field enemiesOnScreen:TList
	
	' Walk
	Field walkX:Float
	Field walkY:Float
	
	' Slots
	Field moveSlots:TSlot[]
	Field otherSlots:TSlot[]
	
	' Skills
	Field runningSkills:TList
	
	' Particle groups
	Field grpSkillEffects:TParticleGroup[]
	Field grpPostEffects:TParticleGroup
	Field grpGuiEffects:TParticleGroup
	
	' Sound
	Field soundMgr:TSoundManager
	Field chanEffects:TSoundChannel
	
	' GUI
	Field gui:TGUI
	Field guiFont:TImageFont
	Field guiFontTitle:TImageFont
	Field dmgFont:TImageFont
	Field skillView:TGroup
	
	' TODO: Remove hardcoded stuff
	Field deadZone:Float = 0.5
	Field imgMP:TImage
	Field imgMPFull:TImage
	
	' Particles
	Field particleImg:TImage
	Field particleImgWind:TImage
	Field weaponSword:TImage
	
	' Status
	Field statusX:Int, statusY:Int
	Field tileX:Int, tileY:Int
	
	' Clear particles (timed)
	Field lastParticleClear:Int
	
	' Init
	Method Init()
		' Frame counter
		frameCounter = TFrameRate.Create()
		
		' Load map
		Self.map = TTileMap.Create()
		Self.map.LoadINI(FS_ROOT + "data/engine.ini")
		Self.map.SetScreenSize(game.gfxWidth, game.gfxHeight)
		Self.map.InitTextureAtlas()
		Self.map.LoadLayers(FS_ROOT + "data/layer-")
		Self.map.Load(FS_ROOT + "data/maps/lyphia.map")
		
		' Enemies
		Self.enemiesOnScreen = CreateList()
		
		' Walk
		Self.walkX = 0
		Self.walkY = 0
		
		' Skills
		Self.runningSkills = CreateList()
		
		' Particle groups
		Self.grpSkillEffects = New TParticleGroup[Self.map.height]
		Self.grpPostEffects = TParticleGroup.Create()
		Self.grpGuiEffects = TParticleGroup.Create() 
		
		For Local I:Int = 0 Until Self.map.height
			Self.grpSkillEffects[I] = TParticleGroup.Create()
		Next
		
		' Walk
		Self.statusX = 80 + 16
		Self.statusY = 80 + 16
		
		' TODO: Remove hardcoded stuff
		SetMaskColor 255, 255, 255
		Self.particleImg = LoadImage(FS_ROOT + "data/skills/particle-3.png")
		MidHandleImage Self.particleImg
		Self.particleImgWind = LoadImage(FS_ROOT + "data/skills/particle-wind.png")
		MidHandleImage Self.particleImgWind
		Self.weaponSword = LoadImage(FS_ROOT + "data/weapons/sword-7.png", MASKEDIMAGE)
		SetImageHandle Self.weaponSword, 4, 32 '2, 27
		
		' Create player
		Self.player = TPlayer.Create("")
		' TODO: player.SetPosition()
		Self.player.x = Self.map.startX * Self.map.tileSizeX
		Self.player.y = Self.map.startY * Self.map.tileSizeY
		Self.player.SetSpeed(0.2)
		Self.player.SetMPEffectFunc(TGameStateInGame.AddMPEffect)
		
		' TODO: Remove hardcoded stuff
		Self.moveSlots = New TSlot[5]
		Self.otherSlots = New TSlot[2]
		
		' Move slots
		Self.moveSlots[0] = TSlot.Create(TActionMove.Create(TAnimationWalk.DIRECTION_UP))
		Self.moveSlots[0].AddTrigger(TKeyTrigger.Create(KEY_UP, True))
		
		Self.moveSlots[1] = TSlot.Create(TActionMove.Create(TAnimationWalk.DIRECTION_DOWN))
		Self.moveSlots[1].AddTrigger(TKeyTrigger.Create(KEY_DOWN, True))
		
		Self.moveSlots[2] = TSlot.Create(TActionMove.Create(TAnimationWalk.DIRECTION_LEFT))
		Self.moveSlots[2].AddTrigger(TKeyTrigger.Create(KEY_LEFT, True))
		
		Self.moveSlots[3] = TSlot.Create(TActionMove.Create(TAnimationWalk.DIRECTION_RIGHT))
		Self.moveSlots[3].AddTrigger(TKeyTrigger.Create(KEY_RIGHT, True) ) 
		
		Self.moveSlots[4] = TSlot.Create(TActionLockDirection.Create())
		Self.moveSlots[4].AddTrigger(TKeyTrigger.Create(KEY_LSHIFT, True))
		
		' Tech slots
		Self.player.techSlots[0] = TSlot.Create()'SChainLightning.Create(Self.player))
		Self.player.techSlots[0].AddTrigger(TKeyTrigger.Create(KEY_Q))
		
		Self.player.techSlots[1] = TSlot.Create()
		Self.player.techSlots[1].AddTrigger(TKeyTrigger.Create(KEY_W))
		
		Self.player.techSlots[2] = TSlot.Create()
		Self.player.techSlots[2].AddTrigger(TKeyTrigger.Create(KEY_E))
		
		Self.player.techSlots[3] = TSlot.Create(SHealingWind.Create(Self.player))
		Self.player.techSlots[3].AddTrigger(TKeyTrigger.Create(KEY_R))
		
		Self.player.techSlots[4] = TSlot.Create(SIcyRays.Create(Self.player))
		Self.player.techSlots[4].AddTrigger(TKeyTrigger.Create(KEY_A))
		
		Self.player.techSlots[5] = TSlot.Create(SThunderSphere.Create(Self.player))
		Self.player.techSlots[5].AddTrigger(TKeyTrigger.Create(KEY_S))
		
		Self.player.techSlots[6] = TSlot.Create(SIceWave.Create(Self.player))
		Self.player.techSlots[6].AddTrigger(TKeyTrigger.Create(KEY_D))
		
		Self.player.techSlots[7] = TSlot.Create(SFireBall.Create(Self.player))
		Self.player.techSlots[7].AddTrigger(TKeyTrigger.Create(KEY_F))
		
		Self.player.techSlots[8] = TSlot.Create(SSwordSlash.Create(Self.player))
		Self.player.techSlots[8].AddTrigger(TKeyTrigger.Create(KEY_V))
		
		' GUI
		Self.InitResources() 
		Self.InitGUI() 
		Self.InitSounds()
	End Method
	
	' InitResources
	Method InitResources()
		' Load fonts
		Self.guiFont = LoadImageFont("Cartoon Bold.ttf", 12, SMOOTHFONT)
		Self.guiFontTitle = LoadImageFont("Cartoon Bold.ttf", 16, SMOOTHFONT)
		Self.dmgFont = LoadImageFont("Cassia Regular.ttf", 24, SMOOTHFONT)
		
		' Status graphics
		Self.imgMP = LoadAnimImage(FS_ROOT + "data/status/mp.png", 118, 1, 0, 118) 
		Self.imgMPFull = LoadImage(FS_ROOT + "data/status/mp-full.png")
		MidHandleImage Self.imgMP
		MidHandleImage Self.imgMPFull
	End Method
	
	' InitGUI
	Method InitGUI()
		Self.gui = TGUI.Create()
		
		' Skill view
		Self.skillView = TGroup.Create("skillView", "Skill view")
		Self.skillView.SetPosition(0.5, 1)
		Self.skillView.SetPositionAbs(- 48 * 4.5, - 48)
		Self.skillView.SetSize(0, 0)
		Self.skillView.SetSizeAbs(48 * 9, 48)
		Self.skillView.SetColor(255, 255, 255)
		Self.gui.root.Add(Self.skillView)
		
		' TODO: Remove hardcoded stuff
		For Local I:Int = 0 To 8
			Self.skillView.Add(TImageBox.Create("skill" + I, Null, 48 * I, 0))
		Next
		
		' View skills
		For Local I:Int = 0 To 8
			If Self.player.techSlots[I] <> Null
				Local skill:TSkill = TSkill(Self.player.techSlots[I].GetAction())
				If skill <> Null
					TImageBox(Self.skillView.GetChild("skill" + I)).SetImage(skill.img)
				EndIf
			EndIf
		Next
		
		' Cursors
		Self.gui.SetCursor("default")
		
		' Apply font to all widgets
		Self.gui.SetFont(Self.guiFont)
		
		' Fonts
		'Self.infoWindow.SetFont(Self.guiFontTitle)
	End Method
	
	' InitSounds
	Method InitSounds()
		Self.soundMgr = TSoundManager.Create() 
		
		Self.chanEffects = Self.soundMgr.AddChannel("Effects") 
		Self.chanEffects.AddSoundDirectory(FS_ROOT + "data/sfx/sounds/")
	End Method
	
	' Update
	Method Update()
		' Update
		frameCounter.Update()
		
		' Input
		Self.UpdateInput()
		Self.player.Update()
		
		' Clear screen
		Cls
		
		' Reset color, alpha, rotation
		ResetMax2D()
		
		' Draw
		Self.map.Draw(TGameStateInGame.OnMapRow)
		
		' Update casting of the current skill
		If Self.player.castingSkill <> Null
			'Print "Casting " + GetTypeName(Self.player.castingSkill)
			Self.UpdateSkillCast()
		EndIf
		
		' Map offset
		SetOrigin Int(Self.map.originX), Int(Self.map.originY)
		
		' AI
		Self.UpdateEnemies()
		
		' Update currently running skills
		Self.UpdateRunningSkills()
		
		' Damage numbers
		Self.DrawDamageNumbers()
		
		' Back to normal offset
		SetOrigin 0, 0
		
		' Post particle effects
		grpPostEffects.Draw()
		
		' Info
		ResetMax2D()
		
		SetImageFont Self.guiFont
		DrawText "FPS: " + frameCounter.GetFPS(), 5, 5 + 190
		DrawText "Average FPS: " + Int(frameCounter.GetAverageFPS()), 5, 20 + 190
		
		?Debug
		DrawText "Player: " + Self.player.PositionToString(), 5, 35 + 190
		DrawText "Move: " + Self.walkX + ", " + Self.walkY, 5, 50 + 190
		DrawText "Tile: " + Self.tileX + ", " + Self.tileY, 5, 65 + 190
		DrawText "Offset: " + Self.map.GetRealOffsetX() + ", " + Self.map.GetRealOffsetY(), 5, 80 + 190
		DrawText "Enemies: " + Self.enemiesOnScreen.Count(), 5, 95 + 190
		DrawText "Particles: " + TParticle.count, 5, 110 + 190
		DrawText "Animation: " + GetTypeName(Self.player.currentAnimation), 5, 125 + 190
		DrawText "Anim.Frame: " + Self.player.currentAnimation.frame, 5, 140 + 190
		?
		
		' Status
		Self.DrawStatus()
		
		' GUI
		'Self.gui.SetAlpha 1
		Self.gui.Draw()
		grpGuiEffects.Draw()
		
		' Skill Outline
		Local skill:TSkill
		For Local I:Int = 0 To 8
			SetAlpha 1
			SetColor 0, 0, 0
			DrawRectOutline Self.skillView.rX + I * 48 - 1, Self.skillView.rY, 48 + 1, 48
			
			' TODO: Remove hardcoded stuff
			SetColor 255, 255, 255
			Select I
				Case 0
					DrawText "Q", Self.skillView.rX + 3, Self.skillView.rY + 3
				Case 1
					DrawText "W", Self.skillView.rX + 48 * 1 + 3, Self.skillView.rY + 3
				Case 2
					DrawText "E", Self.skillView.rX + 48 * 2 + 3, Self.skillView.rY + 3
				Case 3
					DrawText "R", Self.skillView.rX + 48 * 3 + 3, Self.skillView.rY + 3
				Case 4
					DrawText "A", Self.skillView.rX + 48 * 4 + 3, Self.skillView.rY + 3
				Case 5
					DrawText "S", Self.skillView.rX + 48 * 5 + 3, Self.skillView.rY + 3
				Case 6
					DrawText "D", Self.skillView.rX + 48 * 6 + 3, Self.skillView.rY + 3
				Case 7
					DrawText "F", Self.skillView.rX + 48 * 7 + 3, Self.skillView.rY + 3
				Case 8
					DrawText "V", Self.skillView.rX + 48 * 8 + 3, Self.skillView.rY + 3
			End Select
			
			skill = TSkill(Self.player.techSlots[I].GetAction())
			If skill <> Null
				If skill = Self.player.castingSkill
					SetAlpha 0.05 + (1 - skill.GetCastProgress()) * 0.2
					SetColor 255, 255, 255
					DrawRect Self.skillView.rX + I * 48, Self.skillView.rY, 48, 48
				EndIf
				
				If skill.cooldown > 0
					SetAlpha 0.5
					SetColor 0, 0, 0
					DrawRect Self.skillView.rX + I * 48, Self.skillView.rY + skill.GetCooldownProgress() * 48, 48, skill.GetCooldownProgressLeft() * 48
				EndIf
			EndIf
		Next
		
		' Swap buffers
		Flip game.vsync
		
		' Clear particles out of screen
		If MilliSecs() - Self.lastParticleClear > 500
			Self.UpdateParticlesOutOfScreen()
			Self.lastParticleClear = MilliSecs()
		EndIf
	End Method
	
	' UpdateInput
	Method UpdateInput()
		' Update input system
		TInputSystem.Update()
		
		' Reset walk speed
		Self.walkX = 0
		Self.walkY = 0
		
		' Check triggers
		UpdateSlots(Self.moveSlots)
		
		' Update skill slots if not casting
		If Self.player.castingSkill = Null
			UpdateSlots(Self.player.techSlots)
		EndIf
		
		' Walk
		If (Self.player.castingSkill = Null Or Self.player.castingSkill.canMoveWhileCasting) 'And Self.player.currentAnimation <> Self.player.animAttack
			Local tileXNext:Int, tileYNext:Int
			
			' Collision test
			If Self.map.GetTileCoordsDirect(Self.player.GetMidX(), Self.player.y + Self.player.img.height - 1, Self.tileX, Self.tileY)
				Self.map.GetTileCoordsDirect(Self.player.GetMidX() + Self.walkX, Self.player.y + Self.walkY + Self.player.img.height - 1, tileXNext, tileYNext)
				
				Local moveX:Int = True
				Local moveY:Int = True
				
				' TODO: Check all layers
				If Self.tileX <> tileXNext
					If walkX > 0 And Self.map.GetTileType(0, tileXNext, Self.tileY).mLeft = False
						moveX = False
					ElseIf walkX < 0 And Self.map.GetTileType(0, tileXNext, Self.tileY).mRight = False
						moveX = False
					EndIf
				EndIf
				
				If Self.tileY <> tileYNext
					If walkY > 0 And Self.map.GetTileType(0, Self.tileX, tileYNext).mTop = False
						moveY = False
					ElseIf walkY < 0 And Self.map.GetTileType(0, Self.tileX, tileYNext).mBottom = False
						moveY = False
					EndIf
				EndIf
				
				Self.player.Walk(Self.walkX, Self.walkY, moveX, moveY) 
			EndIf
		EndIf
		
		' Adjust map offset
		Self.map.SetOffset(Self.player.x - game.gfxWidthHalf, Self.player.y - game.gfxHeightHalf) 
		Self.map.CorrectOffset()
		
		' Ctrl
		If TInputSystem.GetKeyDown(KEY_LCONTROL) 
			' Ctrl + E = Open Editor
			If TInputSystem.GetKeyHit(KEY_E)
				game.SetGameStateByName("Editor")
			EndIf
		EndIf
		
		' Quit
		If TInputSystem.GetKeyHit(KEY_ESCAPE)
			' TODO: Set "main menu" state
			game.SetGameState(Null)
		EndIf
	End Method
	
	' UpdateSkillCast
	Method UpdateSkillCast()
		Local skill:TSkill = Self.player.castingSkill
		Local progress:Float = skill.GetCastProgress()
		skill.Cast() 
		
		' TODO: Remove hardcoded stuff
		If progress < 1.0
			DrawRect game.gfxWidthHalf - 100, game.gfxHeightHalf + 200, progress * 200, 24
			DrawRectOutline game.gfxWidthHalf - 100, game.gfxHeightHalf + 200, 200, 24
		Else
			skill.Start()
			Self.player.EndCast()
		EndIf
	End Method
	
	' UpdateRunningSkills
	Method UpdateRunningSkills()
		For Local skill:TSkillInstance = EachIn Self.runningSkills
			skill.Run()
		Next
	End Method
	
	' FindVisibleEnemies
	Method FindVisibleEnemies()
		Local h:Int
		Local enemySpawn:TEnemySpawn
		Local enemy:TEnemy
		
		For h = Self.map.tileTop To Self.map.tileBottom
			For enemySpawn = EachIn gsInGame.map.enemySpawns[h]
				enemy = enemySpawn.enemy
				If enemy <> Null
					If enemy.link = Null
						enemy.link = Self.enemiesOnScreen.AddLast(enemy)
					EndIf
				EndIf
			Next
		Next
	End Method
	
	' UpdateEnemies
	Method UpdateEnemies()
		' Collision with an enemy
		Local enemy:TEnemy
		
		' Check for new enemies
		If 1 'Self.walkX <> 0 Or Self.walkY <> 0
			Self.FindVisibleEnemies()
		EndIf
		
		' Update visible enemies
		For enemy = EachIn Self.enemiesOnScreen
			If enemy.x + enemy.img.width < Self.map.tileLeft * Self.map.tileSizeX Or enemy.x > Self.map.tileRight * Self.map.tileSizeX Or ..
				enemy.y + enemy.img.height < Self.map.tileTop * Self.map.tileSizeY Or enemy.y > Self.map.tileBottom * Self.map.tileSizeY
				' Remove enemy from visible enemies list
				enemy.link.Remove()
				enemy.link = Null
				Continue
			End If
				
			enemy.Update()
			
			If enemy.target <> Null
				If enemy.path = Null
					Local eX:Int, eY:Int
					If Self.map.GetTileCoordsDirect(enemy.x, enemy.y, eX, eY)
						If enemy.hasRangeSkills
							' Ranged
							' Find nearest position on line to cast from
							Local distX:Int = enemy.x - enemy.target.x
							Local distY:Int = enemy.y - enemy.target.y
							If distX < distY
								enemy.path = TNode.AStar(4, eX, eY, enemy.target.x / Self.map.tileSizeX, eY, Self.map.cost, 0, - 1, True, ASWEIGHTENED | ASSMOOTHENED | ASCLIMBNFALL, 0)
							Else
								enemy.path = TNode.AStar(4, eX, eY, eX, enemy.target.y / Self.map.tileSizeY, Self.map.cost, 0, - 1, True, ASWEIGHTENED | ASSMOOTHENED | ASCLIMBNFALL, 0)
							EndIf
						Else
							' Melee
							enemy.path = TNode.AStar(8, eX, eY, enemy.target.x / Self.map.tileSizeX, enemy.target.y / Self.map.tileSizeY, Self.map.cost, 0, - 1, True, ASWEIGHTENED | ASSMOOTHENED | ASCLIMBNFALL, 0)
						EndIf
						
						' Delete first node
						If enemy.path <> Null
							TNode(enemy.path.First()).Close()
						EndIf
					EndIf
				Else
					' Path already calculated, find the way to the target
					Local sameLine:Int = TEntity.OnSameLine(enemy, enemy.target)
					
					' Update tech slots
					If enemy.castingSkill = Null
						UpdateSlots(enemy.techSlots)
					EndIf
					
					If enemy.hasRangeSkills And sameLine
						' Turn to the right direction to face the target
						Select sameLine
							' Vertical
							Case 1
								If enemy.target.y < enemy.y
									enemy.SetDirection(TEntity.DIRECTION_UP)
								Else
									enemy.SetDirection(TEntity.DIRECTION_DOWN)
								EndIf
								
							' Horizontal
							Case 2
								If enemy.target.x < enemy.x
									enemy.SetDirection(TEntity.DIRECTION_LEFT)
								Else
									enemy.SetDirection(TEntity.DIRECTION_RIGHT)
								EndIf
						End Select
					ElseIf enemy.castingSkill = Null Or enemy.castingSkill.canMoveWhileCasting
						' Melee
						Local nextNode:TNode = TNode(enemy.path.First())
						
						If nextNode = Null
							enemy.path = Null
						Else
							Local nX:Int = nextNode.x * Self.map.tileSizeX
							Local nY:Int = nextNode.y * Self.map.tileSizeY
							Local nXS:Int = nX + Self.map.tileSizeX / 2 - enemy.x
							Local nYS:Int = nY + Self.map.tileSizeY / 2 - enemy.y
							
							If DistanceSq2(nXS, nYS) < 9
								nextNode.Close()
							EndIf
							
							nXS = Sgn(nXS)
							nYS = Sgn(nYS)
							
							'enemy.MoveTo(nX + Self.map.tileSizeX / 2, nY + Self.map.tileSizeY / 2)
							
							'DrawText GetTypeName(enemy.currentAnimation), enemy.x, enemy.y
							
							If nXS <> 0 Or nYS <> 0
								enemy.Walk(nXS * game.speed, nYS * game.speed)
								
								If enemy.castingSkill = Null
									enemy.UpdateDirection(nXS, nYS)
									enemy.animWalk.Play()
								EndIf
							EndIf
						EndIf
					EndIf
				EndIf
			EndIf
			
			' Skill slots
			If enemy.castingSkill <> Null
				Local progress:Float = enemy.castingSkill.GetCastProgress()
				enemy.castingSkill.Cast()
				
				If progress >= 1.0
					enemy.castingSkill.Start()
					enemy.EndCast()
				EndIf
			EndIf
		Next
	End Method
	
	' DrawDamageNumbers
	Method DrawDamageNumbers()
		SetImageFont Self.dmgFont
		For Local dmg:TDamageView = EachIn TDamageView.list
			dmg.Draw(game.speed)
		Next
	End Method
	
	' DrawStatus
	Method DrawStatus() 
		Self.DrawHP() 
		Self.DrawMP()
	End Method
	
	' DrawHP
	Method DrawHP()
		If Self.player.hp = 0
			Return
		End If
		
		' Really hardcoded...
		Local positionX:Int = 80 + 16
		Local positionY:Int = 80 + 16
		Local limit:Int
		
		SetLineWidth 2
		For Local size:Int = 65 To 80
			SetColor 255, (size - 65) * 10, 0
			
			'SetAlpha 0.5
			limit = 90 + (Self.player.hp / Self.player.maxHP) * 358
			For Local I:Int = 90 To limit Step 2
				DrawLine positionX + CosFastSec(I) * size, positionY - SinFastSec(I) * size, positionX + Cos(I + 2) * size, positionY - Sin(I + 2) * size
			Next
		Next
		SetLineWidth 1
	End Method
	
	' DrawMP
	Method DrawMP() 
		' TODO: Remove hardcoded stuff
		Local limit:Int = 118 - (Self.player.mp / Self.player.maxMP) * 118
		SetColor 255, 255, 255
		SetAlpha 0.5
		
		For Local I:Int = 0 Until limit
			DrawImage Self.imgMP, 16 + 80, 16 + 80 - 118 / 2 + I, I
		Next
		
		SetAlpha 1
		For Local I:Int = limit Until 118
			DrawImage Self.imgMP, 16 + 80, 16 + 80 - 118 / 2 + I, I
		Next
	End Method
	
	' AddMPEffect
	Function AddMPEffect() 
		Local maxCircles:Float = 4
		For Local I:Int = 0 To maxCircles
			Local degree:Int = Rand(0, 359)
			TParticleTween.Create(..
				gsInGame.grpPostEffects,..
				500,..
				gsInGame.imgMPFull,..
				gsInGame.statusX, gsInGame.statusY,..
				gsInGame.statusX, gsInGame.statusY,..
				0.25, 0.01,..
				degree, degree,..
				1, 1 + (0.3) * I / maxCircles,..
				1, 1 + (0.3) * I / maxCircles,..
				255, 255, 255,..
				255, 255, 255..
				..
			)
		Next
	End Function
	
	' GetEffectGroup
	Method GetEffectGroup:TParticleGroup(yPos:Int)
		If Self.player.animWalk.GetDirection() = TAnimationWalk.DIRECTION_UP
			yPos :- Self.map.tileSizeY / 2
		Else
			yPos :+ Self.map.tileSizeY / 2
		EndIf
		
		Local row:Int = yPos / Self.map.tileSizeY
		If row < 0
			row = 0
		EndIf
		If row >= Self.map.height
			row = Self.map.height - 1
		EndIf
		Return Self.grpSkillEffects[row]
	End Method
	
	' Remove
	Method Remove()
		
	End Method
	
	' ToString
	Method ToString:String()
		Return "InGame"
	End Method
	
	' OnMapRow
	Function OnMapRow(row:Int)
		If row = gsInGame.tileY
			gsInGame.player.Draw()
		EndIf
		
		' TODO: Optimize this
		For Local enemy:TEnemy = EachIn gsInGame.enemiesOnScreen
			If row = Int(enemy.y / gsInGame.map.tileSizeY)
				enemy.Draw()
			EndIf
		Next
		
		gsInGame.grpSkillEffects[row].Draw()
		ResetMax2D()
	End Function
	
	' UpdateParticlesOutOfScreen
	Method UpdateParticlesOutOfScreen()
		Local I:Int
		
		For I = Max(0, gsInGame.map.tileTop - 40) Until gsInGame.map.tileTop
			Self.grpSkillEffects[I].Update()
		Next
		
		For I = gsInGame.map.tileBottom Until Min(gsInGame.map.height, gsInGame.map.tileBottom + 40)
			Self.grpSkillEffects[I].Update()
		Next
	End Method
	
	' Create
	Function Create:TGameStateInGame(gameRef:TGame)
		Local gs:TGameStateInGame = New TGameStateInGame
		gameRef.RegisterGameState("InGame", gs)
		Return gs
	End Function
End Type

' TActionMove
Type TActionMove Extends TAction
	Field direction:Int
	
	' Init
	Method Init(nDirection:Int)
		Self.direction = nDirection
	End Method
	
	' Exec
	Method Exec(trigger:TTrigger)
		Select Self.direction
			Case TAnimationWalk.DIRECTION_UP
				gsInGame.walkY :- game.speed
				'gsInGame.map.Scroll(0, -game.speed)
				
			Case TAnimationWalk.DIRECTION_DOWN
				gsInGame.walkY :+ game.speed
				'gsInGame.map.Scroll(0, game.speed)
				
			Case TAnimationWalk.DIRECTION_LEFT
				gsInGame.walkX :- game.speed
				'gsInGame.map.Scroll(-game.speed, 0)
				
			Case TAnimationWalk.DIRECTION_RIGHT
				gsInGame.walkX :+ game.speed
				'gsInGame.map.Scroll(game.speed, 0)
		End Select
		
		Rem
		If TInputSystem.GetKeyDown(KEY_UP) Or JoyY() < -deadZone
			walkY :- game.speed
		EndIf
		If TInputSystem.GetKeyDown(KEY_DOWN) Or JoyY() > deadZone
			walkY :+ game.speed
		EndIf
		If TInputSystem.GetKeyDown(KEY_LEFT) Or JoyX() < -deadZone
			walkX :- game.speed
		EndIf
		If TInputSystem.GetKeyDown(KEY_RIGHT) Or JoyX() > deadZone
			walkX :+ game.speed
		EndIf
		End Rem
	End Method
	
	' Create
	Function Create:TActionMove(nDirection:Int)
		Local action:TActionMove = New TActionMove
		action.Init(nDirection)
		Return action
	End Function
End Type

' TActionEnter
Type TActionEnter Extends TAction
	' Init
	Method Init()
		
	End Method
	
	' Exec
	Method Exec(trigger:TTrigger)
		
	End Method
	
	' Create
	Function Create:TActionEnter()
		Local action:TActionEnter = New TActionEnter
		action.Init()
		Return action
	End Function
End Type

' TActionLockDirection
Type TActionLockDirection Extends TAction
	Field dir:Int = -1
	
	' Init
	Method Init()
		
	End Method
	
	' ExecStart
	Method ExecStart(trigger:TTrigger)
		dir = gsInGame.player.animWalk.GetDirection() 
	End Method
	
	' Exec
	Method Exec(trigger:TTrigger)
		gsInGame.player.animWalk.SetDirection(dir)
		gsInGame.player.animAttack.ApplyDirectionFromWalkAni(gsInGame.player.animWalk)
	End Method
	
	' Create
	Function Create:TActionLockDirection()
		Local action:TActionLockDirection = New TActionLockDirection
		action.Init()
		Return action
	End Function
End Type

' TSkillInstance
Type TSkillInstance
	Const dmgNumYOffset:Int = -46
	
	' Link
	Field link:TLink
	
	' Position
	Field x:Float, y:Float
	
	' Damage
	Field dmg:Int
	Field caster:TEntity
	Field target:TEntity
	
	' The time in millisecs it started
	Field started:Int
	
	' The time it persists
	Field maxRunTime:Int
	
	' Init
	Method InitInstance(castedBy:TEntity)
		Self.link = gsInGame.runningSkills.AddLast(Self) 
		Self.started = MilliSecs() 
		Self.maxRunTime = 0
		
		Self.caster = castedBy
		Self.target = Null
		Self.x = Self.caster.GetMidX()
		Self.y = Self.caster.GetMidY()
	End Method
	
	' SetPosition
	Method SetPosition(nX:Int, nY:Int) 
		Self.x = nX
		Self.y = nY
	End Method
	
	' GetRunTime
	Method GetRunTime:Int() 
		Return MilliSecs() - Self.started
	End Method
	
	' CheckCollision
	' TODO: Return collision status
	Method CheckRectCollision(x:Int, y:Int, width:Int, height:Int)
		If TPlayer(Self.caster)
			' Collision with an enemy
			Local enemy:TEnemy
			
			For enemy = EachIn gsInGame.enemiesOnScreen
				If enemy <> Null
					If RectInRect(x, y, width, height, enemy.x, enemy.y, enemy.img.width, enemy.img.height)
						' Damage
						enemy.LoseHPBy(Self.dmg, Self.caster)
						TDamageView.Create(Self.dmg, enemy.GetMidX() + Rand(- 12, 12), enemy.GetY() - Rand(0, 16) + TSkillInstance.dmgNumYOffset)
						
						Self.target = enemy
						
						Self.OnHit()
					EndIf
				EndIf
			Next
		Else
			Local ctarget:TEntity = Self.caster.target
			If RectInRect(x, y, width, height, ctarget.x, ctarget.y, ctarget.img.width, ctarget.img.height)
				' Damage
				ctarget.LoseHPBy(Self.dmg, Self.caster)
				TDamageView.Create(Self.dmg, ctarget.GetMidX() + Rand(- 12, 12), ctarget.GetY() - Rand(0, 16) + TSkillInstance.dmgNumYOffset, True)
				
				Self.target = ctarget
				
				Self.OnHit()
			EndIf
		EndIf
	End Method
	
	' CheckCircleCollision
	' TODO: Return collision status
	Method CheckCircleCollision(x:Int, y:Int, radius:Int)
		If TPlayer(Self.caster)
			' Collision with an enemy
			Local enemy:TEnemy
			
			For enemy = EachIn gsInGame.enemiesOnScreen
				If enemy <> Null
					If CircleInRect(x, y, radius, enemy.x, enemy.y, enemy.img.width, enemy.img.height)
						' Damage
						enemy.LoseHPBy(Self.dmg, Self.caster)
						TDamageView.Create(Self.dmg, enemy.GetMidX() + Rand(- 12, 12), enemy.GetY() - Rand(0, 16) + TSkillInstance.dmgNumYOffset)
						
						Self.target = enemy
						
						Self.OnHit()
					EndIf
				EndIf
			Next
		Else
			' TODO: Enemies' skill collision
			Local ctarget:TEntity = Self.caster.target
			If CircleInRect(x, y, radius, ctarget.x, ctarget.y, ctarget.img.width, ctarget.img.height)
				' Damage
				ctarget.LoseHPBy(Self.dmg, Self.caster)
				TDamageView.Create(Self.dmg, ctarget.GetMidX() + Rand(- 12, 12), ctarget.GetY() - Rand(0, 16) + TSkillInstance.dmgNumYOffset, True)
				
				Self.target = ctarget
				
				Self.OnHit()
			EndIf
		EndIf
	End Method
	
	' Run
	Method Run() Abstract
	
	' OnHit
	Method OnHit() Abstract
	
	' OnRemove
	Method OnRemove()
		
	End Method
	
	' Remove
	Method Remove()
		Self.OnRemove()
		Self.link.Remove()
	End Method
End Type

' ------------------------------------------------------------
' Skill types
' ------------------------------------------------------------

' TODO: Localization

' TNonElemental
Type TNonElemental Extends TSkill
	' GetMagicType
	Method GetMagicType:String()
		Return "Non-elemental"
	End Method
End Type
Type TNonElementalInstance Extends TSkillInstance
	' GetMagicType
	Method GetMagicType:String()
		Return "Non-elemental"
	End Method
End Type

' TFireMagic
Type TFireMagic Extends TSkill
	' GetMagicType
	Method GetMagicType:String()
		Return "Fire"
	End Method
End Type
Type TFireMagicInstance Extends TSkillInstance
	' GetMagicType
	Method GetMagicType:String()
		Return "Fire"
	End Method
End Type

' TWaterMagic
Type TWaterMagic Extends TSkill
	' GetMagicType
	Method GetMagicType:String()
		Return "Water"
	End Method
End Type
Type TWaterMagicInstance Extends TSkillInstance
	' GetMagicType
	Method GetMagicType:String()
		Return "Water"
	End Method
End Type

' TAirMagic
Type TAirMagic Extends TSkill
	' GetMagicType
	Method GetMagicType:String()
		Return "Air"
	End Method
End Type
Type TAirMagicInstance Extends TSkillInstance
	' GetMagicType
	Method GetMagicType:String()
		Return "Air"
	End Method
End Type

' TEarthMagic
Type TEarthMagic Extends TSkill
	' GetMagicType
	Method GetMagicType:String()
		Return "Earth"
	End Method
End Type
Type TEarthMagicInstance Extends TSkillInstance
	' GetMagicType
	Method GetMagicType:String()
		Return "Earth"
	End Method
End Type

' TLightMagic
Type TLightMagic Extends TSkill
	' GetMagicType
	Method GetMagicType:String()
		Return "Light"
	End Method
End Type
Type TLightMagicInstance Extends TSkillInstance
	' GetMagicType
	Method GetMagicType:String()
		Return "Light"
	End Method
End Type

' TShadowMagic
Type TShadowMagic Extends TSkill
	' GetMagicType
	Method GetMagicType:String()
		Return "Shadow"
	End Method
End Type
Type TShadowMagicInstance Extends TSkillInstance
	' GetMagicType
	Method GetMagicType:String()
		Return "Shadow"
	End Method
End Type

' ------------------------------------------------------------
' Skills
' ------------------------------------------------------------

' TFireBall
Type TFireBall Extends TFireMagicInstance
	Field degree:Int
	
	' Init
	Method Init(castedBy:TEntity)
		Super.InitInstance(castedBy)
		
		Self.degree = Self.caster.GetDegree()
		Self.maxRunTime = 750
		
		Self.dmg = 35
	End Method
	
	' Run
	Method Run()
		If Self.GetRunTime() > Self.maxRunTime
			Self.Remove()
		EndIf
		
		Self.x:+Cos(Self.degree) * 1.25 * game.speed
		Self.y:+Sin(Self.degree) * 1.25 * game.speed
		
		' TODO: Remove hardcoded stuff
		Local degree:Int
		Local direction:Int
		Local damping:Float
		Local fireOffsetCos:Float
		Local fireOffsetSin:Float
		
		For Local I:Int = 0 To game.speed
			degree = Rand(0, 359)
			direction = ( (I Mod 2) * 2 - 1) ' -1 or 1 depending on I
			damping = (1.0 - Self.GetRunTime() / Float(maxRunTime))
			damping = damping * damping
			fireOffsetCos = -Cos(Self.degree) * (I / Float(game.speed + 0.001)) * game.speed + damping * CosFastSec(Self.GetRunTime() + 90) * 12 * direction
			fireOffsetSin = -Sin(Self.degree) * (I / Float(game.speed + 0.001)) * game.speed + damping * SinFastSec(Self.GetRunTime()) * 12 * direction * perspectiveFactor
			
			TParticleTween.Create(..
				gsInGame.GetEffectGroup(Self.y + fireOffsetSin),..
				200,..
				gsInGame.particleImg,..
				Self.x + fireOffsetCos, Self.y - Sin(Self.degree) * (I / Float(game.speed)) * game.speed + fireOffsetSin,  ..
				Self.x + fireOffsetCos + CosFast[degree] * Rand(5, 20), Self.y + fireOffsetSin + SinFast[degree] * Rand(5, 20),  ..
				0.5, 0.1,..
				0, 360,..
				0.5, 0.75,..
				0.5, 0.75,..
				255, 128 + Rand(0, 127) * direction, 0,..
				192, 180, 160..
				..
			)
		Next
		
		' Collision
		Self.CheckRectCollision(Self.x - 3, Self.y - 3, 6, 6)
	End Method
	
	' OnHit
	Method OnHit()
		Self.Remove()
	End Method
	
	' Create
	Function Create:TFireBall(castedBy:TEntity)
		Local skill:TFireBall = New TFireBall
		skill.Init(castedBy)
		Return skill
	End Function
End Type

' SFireBall
Type SFireBall Extends TFireMagic
	' Init
	Method Init(nCaster:TEntity) 
		Super.Init(nCaster)
		Self.cooldown = 50
		Self.castTime = 250
		Self.canMoveWhileCasting = True
		Self.mpCostAbs = 8
	End Method
	
	' Cast
	Method Cast()
		Self.caster.Cast(Self)
		
		' TODO: Remove hardcoded stuff
		Local degree:Int
		For Local i:Int = 0 To game.speed / 8
			degree = Rand(0, 359)
			TParticleTween.Create(..
				gsInGame.GetEffectGroup(Self.caster.GetMidY()),  ..
				250,..
				gsInGame.particleImg,..
				Self.caster.GetMidX(), Self.caster.GetMidY(),  ..
				Self.caster.GetMidX() + CosFast[degree] * Rand(5, 10), Self.caster.GetMidY() + SinFast[degree] * Rand(5, 10),  ..
				0.5, 0.1,..
				0, 360,..
				0.5, 0.75,..
				0.5, 0.75,..
				255, 0, 0,..
				192, 180, 160..
				..
			)
		Next
	End Method
	
	' Use
	Method Use()
		gsInGame.chanEffects.Play("FireBall")
		
		TFireBall.Create(Self.caster)
	End Method
	
	' GetName
	Method GetName:String() 
		' TODO: Localization
		Return "Fire Ball"
	End Method
	
	' Create
	Function Create:TSkill(nCaster:TEntity)
		Local skill:TSkill = New SFireBall
		skill.Init(nCaster)
		Return skill
	End Function
End Type

' TIceWave
Type TIceWave Extends TWaterMagicInstance
	Const circleSteps:Int = 10
	
	Field maxCircles:Int
	Field circlesDistance:Int
	Field lastHit:Int
	
	' Init
	Method Init(castedBy:TEntity)
		Super.InitInstance(castedBy)
		
		Self.maxRunTime = 1000
		Self.maxCircles = 12
		Self.circlesDistance = 12
		
		' Per hit
		Self.dmg = 10
	End Method
	
	' Run
	Method Run() 
		Local runtime:Int = Self.GetRunTime()
		
		If runtime > Self.maxRunTime
			Self.Remove()
		EndIf
		
		Local circle:Int = runtime / (Self.maxRunTime / maxCircles)
		
		For Local I:Int = 0 To game.speed / 8
			For Local degree:Int = 0 To 359 Step TIceWave.circleSteps
				TParticleTween.Create(..
					gsInGame.GetEffectGroup(Self.y + SinFast[degree] * perspectiveFactor * circlesDistance * circle),..
					400,..
					gsInGame.particleImg,..
					Self.x + CosFast[degree] * circlesDistance * circle, Self.y + SinFast[degree] * perspectiveFactor * circlesDistance * circle, ..
					Self.x + CosFast[degree] * circlesDistance * circle, Self.y + SinFast[degree] * perspectiveFactor * circlesDistance * circle - Rand(6, 6 + (maxCircles - circle) * 24),..
					0.5, 0.1,..
					Rand(0, 360), Rand(0, 360),..
					0.75, 1.25,..
					0.75, 1.25,..
					255, 255, 255,..
					192, 224, 255..
					..
				)
			Next
		Next
		
		' Collision
		If MilliSecs() - Self.lastHit >= 50
			Self.CheckCircleCollision(Self.x, Self.y, circlesDistance * circle)
			Self.lastHit = MilliSecs()
		EndIf
	End Method
	
	' OnHit
	Method OnHit()
		
	End Method
	
	' Create
	Function Create:TIceWave(castedBy:TEntity)
		Local skill:TIceWave = New TIceWave
		skill.Init(castedBy)
		Return skill
	End Function
End Type

' SIceWave
Type SIceWave Extends TWaterMagic
	' Init
	Method Init(nCaster:TEntity)
		Super.Init(nCaster)
		Self.cooldown = 7000
		Self.castTime = 500
		Self.canMoveWhileCasting = False
		Self.mpCostAbs = 60
	End Method
	
	' Cast
	Method Cast() 
		Self.caster.Cast(Self)
		
		' TODO: Remove hardcoded stuff
		Local degree:Int
		For Local i:Int = 0 To game.speed / 8
			degree = Rand(0, 359)
			TParticleTween.Create(..
				gsInGame.GetEffectGroup(Self.caster.GetMidY()),  ..
				250,..
				gsInGame.particleImg,..
				Self.caster.GetMidX(), Self.caster.GetMidY(),  ..
				Self.caster.GetMidX() + CosFast[degree] * Rand(5, 10), Self.caster.GetMidY() + SinFast[degree] * Rand(5, 10),  ..
				0.5, 0.1,..
				0, 360,..
				0.5, 0.75,..
				0.5, 0.75,..
				255, 255, 255,..
				192, 224, 255..
				..
			)
		Next
		
	End Method
	
	' Use
	Method Use() 
		gsInGame.chanEffects.Play("IceWave")
		
		TIceWave.Create(Self.caster)
	End Method
	
	' GetName
	Method GetName:String() 
		' TODO: Localization
		Return "Ice Wave"
	End Method
	
	' Create
	Function Create:TSkill(nCaster:TEntity)
		Local skill:TSkill = New SIceWave
		skill.Init(nCaster)
		Return skill
	End Function
End Type

' TThunderSphere
Type TThunderSphere Extends TAirMagicInstance
	Field degree:Int
	
	' Init
	Method Init(castedBy:TEntity)
		Super.InitInstance(castedBy)
		
		Self.degree = Self.caster.GetDegree()
		Self.maxRunTime = 750
		
		Self.dmg = 100
	End Method
	
	' Run
	Method Run()
		If Self.GetRunTime() > Self.maxRunTime
			Self.Remove()
		EndIf
		
		Self.x:+Cos(Self.degree) * 1.25 * game.speed
		Self.y:+Sin(Self.degree) * 1.25 * game.speed
		
		For Local I:Int = 0 To game.speed
			Local pDegree:Int = Rand(0, 359)
			TParticleTween.Create( ..
				gsInGame.GetEffectGroup(Self.y),  ..
				500,  ..
				gsInGame.particleImg,..
				Self.x, Self.y,  ..
				Self.x + CosFast[pDegree] * Rand(20, 80), Self.y + SinFast[pDegree] * Rand(20, 80),  ..
				0.5, 0.01,  ..
				pDegree, pDegree,  ..
				0.95, 0.75,  ..
				0.95, 0.1,  ..
				255, 255, 0,  ..
				192, 192, 0 ..
				..
			)
		Next
		
		' Collision
		Self.CheckRectCollision(Self.x - 4, Self.y - 4, 8, 8)
	End Method
	
	' OnHit
	Method OnHit()
		Self.Remove()
	End Method
	
	' Create
	Function Create:TThunderSphere(castedBy:TEntity)
		Local skill:TThunderSphere = New TThunderSphere
		skill.Init(castedBy)
		Return skill
	End Function
End Type

' SThunderSphere
Type SThunderSphere Extends TAirMagic
	' Init
	Method Init(nCaster:TEntity) 
		Super.Init(nCaster)
		Self.cooldown = 1000
		Self.castTime = 1000
		Self.canMoveWhileCasting = True
		Self.mpCostAbs = 35
	End Method
	
	' Cast
	Method Cast() 
		Self.caster.Cast(Self)
		
		' TODO: Remove hardcoded stuff
		Local degree:Int
		For Local i:Int = 0 To game.speed / 8
			degree = Rand(0, 359)
			TParticleTween.Create(..
				gsInGame.GetEffectGroup(gsInGame.player.GetMidY()),..
				250,..
				gsInGame.particleImg,..
				Self.caster.GetMidX(), Self.caster.GetMidY(),  ..
				Self.caster.GetMidX() + CosFast[degree] * Rand(20, 40), Self.caster.GetMidY() + SinFast[degree] * Rand(20, 40),  ..
				0.5, 0.1,..
				0, 360,..
				0.5, 0.75,..
				0.5, 0.75,..
				255, 255, 0,  ..
				192, 180, 160..
				..
			)
			
			Local castRadius:Int = 64
			For Local a:Int = -1 To 1 Step 2
				TParticleTween.Create( ..
					gsInGame.GetEffectGroup(Self.caster.GetMidY() + a * SinFastSec(Self.GetCastProgress() * 360) * castRadius * Self.GetCastProgressLeft()),  ..
					400,  ..
					gsInGame.particleImg,..
					Self.caster.GetMidX() + a * CosFastSec(Self.GetCastProgress() * 360) * castRadius * Self.GetCastProgressLeft(), Self.caster.GetMidY() + a * SinFastSec(Self.GetCastProgress() * 360) * castRadius * Self.GetCastProgressLeft(),  ..
					Self.caster.GetMidX() + a * CosFastSec(Self.GetCastProgress() * 360) * castRadius * Self.GetCastProgressLeft() + CosFast[degree] * Rand(5, 10), Self.caster.GetMidY() + a * SinFastSec(Self.GetCastProgress() * 360) * castRadius * Self.GetCastProgressLeft() + SinFast[degree] * Rand(5, 10),  ..
					0.5, 0.1,..
					0, 360,..
					0.3, 0.5,  ..
					0.3, 0.5,  ..
					255, 255, 0,  ..
					192, 180, 160..
					..
				)
			Next
		Next
	End Method
	
	' Use
	Method Use()
		gsInGame.chanEffects.Play("ThunderSphere")
		
		TThunderSphere.Create(Self.caster)
	End Method
	
	' GetName
	Method GetName:String() 
		' TODO: Localization
		Return "Thunder Sphere"
	End Method
	
	' Create
	Function Create:TSkill(nCaster:TEntity)
		Local skill:TSkill = New SThunderSphere
		skill.Init(nCaster)
		Return skill
	End Function
End Type

' TIcyRays
Type TIcyRays Extends TWaterMagicInstance
	Field degree:Int
	
	' Init
	Method Init(castedBy:TEntity, offsetDegree:Int)
		Super.InitInstance(castedBy)
		
		Self.degree = Self.caster.GetDegree() + offsetDegree
		Self.x:+Cos(Self.degree) * 12
		Self.y:+Sin(Self.degree) * 12
		
		Self.maxRunTime = 300
		
		Self.dmg = 30
	End Method
	
	' Run
	Method Run()
		If Self.GetRunTime() > Self.maxRunTime
			Self.Remove()
		EndIf
		
		Self.x:+Cos(Self.degree) * 1.15 * game.speed
		Self.y:+Sin(Self.degree) * 1.15 * game.speed
		
		For Local I:Int = 0 To game.speed / 4
			Local pDegree:Int = Rand(90 - 23, 90 + 23)
			TParticleTween.Create( ..
				gsInGame.GetEffectGroup(Self.y),  ..
				600,  ..
				gsInGame.particleImg,..
				Self.x, Self.y,  ..
				Self.x + CosFast[pDegree] * Rand(6, 25), Self.y - SinFast[pDegree] * Rand(6, 25) * perspectiveFactor,  ..
				0.5, 0.01,  ..
				0, 360,  ..
				0.5, 1.75,  ..
				0.5, 1.75,  ..
				255, 255, 255,  ..
				128, 128, 253 ..
				..
			)
		Next
		
		' Collision
		Self.CheckRectCollision(Self.x - 2, Self.y - 2, 4, 4)
	End Method
	
	' OnHit
	Method OnHit()
		Self.Remove()
	End Method
	
	' Create
	Function Create:TIcyRays(castedBy:TEntity, offsetDegree:Int)
		Local skill:TIcyRays = New TIcyRays
		skill.Init(castedBy, offsetDegree)
		Return skill
	End Function
End Type

' SIcyRays
Type SIcyRays Extends TWaterMagic
	' Init
	Method Init(nCaster:TEntity) 
		Super.Init(nCaster)
		Self.cooldown = 1500
		Self.castTime = 0
		Self.canMoveWhileCasting = True
		Self.mpCostAbs = 40
	End Method
	
	' Cast
	Method Cast() 
		Self.caster.Cast(Self)
	End Method
	
	' Use
	Method Use()
		gsInGame.chanEffects.Play("IcyRays")
		
		For Local I:Int = -2 To 2 Step 1
			TIcyRays.Create(Self.caster, I * 7)
		Next
	End Method
	
	' GetName
	Method GetName:String() 
		' TODO: Localization
		Return "Icy Rays"
	End Method
	
	' Create
	Function Create:TSkill(nCaster:TEntity)
		Local skill:TSkill = New SIcyRays
		skill.Init(nCaster)
		Return skill
	End Function
End Type

' TSwordSlash
Type TSwordSlash Extends TNonElementalInstance
	Field degree:Int
	Field lastHit:Int
	Field combo:Int
	Field rotateDirection:Int
	
	' Init
	Method Init(castedBy:TEntity, nCombo:Int = 1)
		Super.InitInstance(castedBy)
		Self.combo = nCombo
		
		Select Self.combo
			' Sword left to right
			Case 1
				Self.degree = Self.caster.GetDegree()
				Self.rotateDirection = 1
				Self.caster.animAttack.slashDirection = 1
			
			' Sword right to left
			Case 2
				Self.degree = Self.caster.GetDegree() + 180
				Self.rotateDirection = -1
				Self.caster.animAttack.slashDirection = -1
		End Select
		
		Self.maxRunTime = 150
		
		Self.dmg = 5
	End Method
	
	' Run
	Method Run()
		If Self.GetRunTime() > Self.maxRunTime
			Self.Remove()
			Return
		EndIf
		
		If Self.caster.currentAnimation <> Self.caster.animAttack
			Self.caster.animAttack.SetFrameDuration(50)
			Self.caster.animAttack.ApplyDirectionFromWalkAni(Self.caster.animWalk)
			Self.caster.SetAnimation(Self.caster.animAttack)
			'Self.caster.currentAnimation.Start()
		EndIf
		
		' Go to nearest enemy
		If Self.target <> Null
			Self.caster.MoveTo(Self.target.x, Self.target.y, game.speed, 26 * 26)
		End If
		
		Local handX:Int = Self.caster.animAttack.GetHandPositionX()
		Local handY:Int = Self.caster.animAttack.GetHandPositionY()
		
		Self.x = Self.caster.GetX() + handX
		Self.y = Self.caster.GetY() + handY
		
		Local rotation:Float
		Local scaleY:Float
		If Self.caster.animAttack.GetDirection() = TAnimationAttack.DIRECTION_LEFT Or Self.caster.animAttack.GetDirection() = TAnimationAttack.DIRECTION_RIGHT
			rotation = Self.degree + Self.rotateDirection * ((Self.GetRunTime() / Float(Self.maxRunTime)) * 60 + 60)
			scaleY = perspectiveFactor
		Else
			rotation = Self.degree + Self.rotateDirection * ((Self.GetRunTime() / Float(Self.maxRunTime)) * 130 + 25)
			scaleY = 1
		EndIf
		
		Const ffInv:Float = 1 / 45.0
		Local rotationOffset:Int
		Local alpha:Float
		For Local i:Int = 0 To game.speed / 4
			If i = 0
				alpha = 1.0
				rotationOffset = 0
			Else
				alpha = 1 - Abs(rotationOffset) * ffInv
				rotationOffset = Rand(1, 45) * Self.rotateDirection
			End If
			TParticleTween.Create( ..
							Self.caster.grpWeaponEffects,  ..
							100,  ..
							gsInGame.weaponSword,  ..
							handX, handY,  ..
							handX, handY,  ..
							alpha, alpha,  ..
							rotation - rotationOffset, rotation,  ..
							Self.rotateDirection * scaleY, Self.rotateDirection * scaleY,  ..
							1.0, 1.0,  ..
							255, 255, 255,  ..
							255, 255, 255 ..
							..
						)
			
			' Skill effect (could be blood e.g.)
			Rem
			TParticleTween.Create( ..
							gsInGame.GetEffectGroup(Self.y),  ..
							100,  ..
							gsInGame.weaponSword,  ..
							Self.x, Self.y,  ..	' TODO: Find end of sword
							Self.x, Self.y,  ..
							0.5, 0.01,  ..
							rotation - rotationOffset * 2, rotation - rotationOffset * 2,  ..
							Self.rotateDirection * scaleY, Self.rotateDirection * scaleY,  ..
							0.5, 0.25, ..
							255, 0, 0,  ..
							255, 0, 0 ..
							..
						)
			End Rem
		Next
					
		' Collision
		If MilliSecs() - Self.lastHit >= 50
			' TODO: Change collision method
			Self.CheckCircleCollision(Self.x, Self.y, gsInGame.weaponSword.height - 6)
			Self.lastHit = MilliSecs()
		EndIf
	End Method
	
	' OnHit
	Method OnHit()
		Self.caster.AddMP(0.7)
		
		If Self.caster.target <> Null
			Self.caster.target.DelayCast(40)
		End If
	End Method
	
	' OnRemove
	Method OnRemove()
		Self.caster.SetAnimation(Self.caster.animWalk)
		'Self.caster.currentAnimation.Play()
	End Method
	
	' Create
	Function Create:TSwordSlash(castedBy:TEntity, nCombo:Int = 1)
		Local skill:TSwordSlash = New TSwordSlash
		skill.Init(castedBy, nCombo)
		Return skill
	End Function
End Type

' SSwordSlash
Type SSwordSlash Extends TNonElemental
	Field comboCounter:Int
	
	' Init
	Method Init(nCaster:TEntity)
		Super.Init(nCaster)
		Self.cooldown = 150
		Self.castTime = 0
		Self.canMoveWhileCasting = True
		Self.comboCounter = 1
		Self.mpCostAbs = 1
	End Method
	
	' Cast
	Method Cast()
		Self.caster.Cast(Self)
	End Method
	
	' Use
	Method Use()
		gsInGame.chanEffects.Play("SwordSlash")
		
		TSwordSlash.Create(Self.caster, Self.comboCounter)
		
		' TODO: Condition
		Self.comboCounter:+1
		If Self.comboCounter > 2
			' Reset
			Self.comboCounter = 1
		End If
	End Method
	
	' GetName
	Method GetName:String() 
		' TODO: Localization
		Return "Sword Slash"
	End Method
	
	' Create
	Function Create:TSkill(nCaster:TEntity)
		Local skill:TSkill = New SSwordSlash
		skill.Init(nCaster)
		Return skill
	End Function
End Type

' SHealingWind
Type SHealingWind Extends TAirMagic
	Field heal:Int
	
	' Init
	Method Init(nCaster:TEntity)
		Super.Init(nCaster)
		Self.cooldown = 5000
		Self.castTime = 1000
		Self.canMoveWhileCasting = False
		Self.mpCostAbs = 50
		Self.heal = 250
	End Method
	
	' Cast
	Method Cast()
		Self.caster.Cast(Self)
		
		For Local i:Int = 0 To game.speed / 2
			Local degree:Int = Rand(0, 359)
			Local offset:Int = Self.caster.img.width + 6
			TParticleTween.Create( ..
						gsInGame.GetEffectGroup(Self.caster.GetY2() - 4 + SinFast[degree] * offset * perspectiveFactor),  ..
						400,  ..
						gsInGame.particleImgWind,  ..
						Self.caster.GetMidX() + CosFast[degree] * offset, Self.caster.GetY2() - 4 + SinFast[degree] * offset * perspectiveFactor,  ..
						Self.caster.GetMidX() + CosFast[degree] * offset, Self.caster.GetY() + SinFast[degree] * offset * perspectiveFactor - 20,  ..
						0.5, 0.1,..
						0, 0,  ..
						0.3, 0.5,  ..
						0.3, 0.5,  ..
						255, 255, 255,  ..
						0, 255, 0 ..
						..
					)
		Next
	End Method
	
	' Use
	Method Use()
		gsInGame.chanEffects.Play("HealingWind")
	
		Self.caster.AddHP(Self.heal)
		Self.CreateHealingView(Self.heal)
		
		For Local degree:Int = 0 Until 360 Step 10
			Local offset:Int = Self.caster.img.width + 6
			TParticleTween.Create( ..
						gsInGame.GetEffectGroup(Self.caster.GetY2() - 4 + SinFast[degree] * offset * 2 * perspectiveFactor),  ..
						Rand(400, 800),  ..
						gsInGame.particleImgWind,  ..
						Self.caster.GetMidX() + CosFast[degree] * offset / 2, Self.caster.GetY2() - 4 + SinFast[degree] * offset / 2 * perspectiveFactor,  ..
						Self.caster.GetMidX() + CosFast[degree] * offset * 2, Self.caster.GetY() + SinFast[degree] * offset * 2 * perspectiveFactor - 20,  ..
						0.5, 0.1,..
						0, 0,  ..
						0.5, 0.85,  ..
						0.5, 0.85,  ..
						255, 255, 255,  ..
						0, 255, 0 ..
						..
					)
		Next
	End Method
	
	' CreateHealingView
	Method CreateHealingView(heal:Int)
		TDamageView.Create(- heal, Self.caster.GetMidX() + Rand(- 12, 12), Self.caster.GetY() - Rand(0, 16) + TSkillInstance.dmgNumYOffset)
	End Method
	
	' GetName
	Method GetName:String() 
		' TODO: Localization
		Return "Healing Wind"
	End Method
	
	' OnHit
	Method OnHit()
		
	End Method
	
	' Create
	Function Create:TSkill(nCaster:TEntity)
		Local skill:TSkill = New SHealingWind
		skill.Init(nCaster)
		Return skill
	End Function
End Type

' SChainLightning
Type SChainLightning Extends TAirMagic
	' Init
	Method Init(nCaster:TEntity)
		Super.Init(nCaster)
		Self.cooldown = 1000
		Self.castTime = 500
		Self.canMoveWhileCasting = False
		Self.mpCostAbs = 50
	End Method
	
	' Cast
	Method Cast() 
		
	End Method
	
	' Use
	Method Use() 
		
	End Method
	
	' GetName
	Method GetName:String() 
		' TODO: Localization
		Return "Chain Lightning"
	End Method
	
	' Create
	Function Create:TSkill(nCaster:TEntity)
		Local skill:TSkill = New SChainLightning
		skill.Init(nCaster)
		Return skill
	End Function
End Type
